package com.threeti.mecool.core.application.acl;

import com.threeti.mecool.core.application.Cache;
import com.threeti.mecool.core.application.project.ProjectService;
import com.threeti.mecool.core.domain.model.acl.Orgnization;
import com.threeti.mecool.core.domain.model.acl.OrgnizationEmployee;
import com.threeti.mecool.core.domain.model.acl.User;
import com.threeti.mecool.core.domain.model.project.Project;
import com.threeti.mecool.core.domain.model.project.ProjectEmployee;
import com.threeti.mecool.core.infrastructure.persistence.project.ProjectRepository;
import com.threeti.mecool.core.util.TreeHelper;
import com.threeti.mecool.legacy.domain.model.acl.EmployeeLegacy;
import com.threeti.mecool.legacy.domain.model.acl.OrgnizationEmployeeLegacy;
import com.threeti.mecool.legacy.domain.model.acl.OrgnizationEmployeeLegacyTreeAdapter;
import com.threeti.mecool.legacy.domain.model.acl.OrgnizationLegacy;
import com.threeti.mecool.legacy.infrastructure.persistence.EmployeeLegacyRepository;
import com.threeti.mecool.legacy.infrastructure.persistence.OrgnizationEmployeeLegacyRepository;
import com.threeti.mecool.legacy.infrastructure.persistence.OrgnizationLegacyRepository;
import com.threeti.mecool.shared.util.TreeNodeWithChilds;
import org.apache.commons.lang.Validate;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Propagation;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.CollectionUtils;

import javax.persistence.EntityManager;
import javax.persistence.PersistenceContext;
import java.io.Serializable;
import java.util.*;

@Service
public class EmployeeServiceImpl implements EmployeeService {
    @PersistenceContext
    EntityManager entityManager;

    @Autowired
    Cache cache;

    @Autowired
    private OrgnizationEmployeeLegacyRepository orgEmpLegacyRepo;

    @Autowired
    private OrgnizationLegacyRepository orgLegacyRepo;

    @Autowired
    private EmployeeLegacyRepository empLegacyRepo;

    @Autowired
    private ProjectRepository projRepo;

    @Autowired
    ProjectService projectService;

    @Override
    public List<Orgnization> findAllOrgnizations() {
        List<OrgnizationLegacy> orgLegs = orgLegacyRepo.findAll();
        List<Orgnization> resultTopLevelNodes = TreeHelper.templateBuildTree(orgLegs, Orgnization.class, orgLegacyRepo);
        return resultTopLevelNodes;
    }

    @Override
    public List<Orgnization> findAllChildOrgnizationsBy(String parentOrgCode) {
        List<OrgnizationLegacy> orgLegs = orgLegacyRepo.findByParentCode(parentOrgCode);
        List<Orgnization> resultNodes = TreeHelper.templateBuildTree(orgLegs, Orgnization.class, orgLegacyRepo, parentOrgCode);
        return resultNodes;
    }

    @Override
    public List<OrgnizationEmployee> findAllChildEmployeesBy(String orgCode) {
        List<OrgnizationEmployeeLegacy> orgLegs = orgEmpLegacyRepo.findByOrgCode(orgCode);
        Map<Long, String> nameReplacedRegistry = new HashMap<Long, String>();
        for (OrgnizationEmployeeLegacy orgLeg : orgLegs) {
            EmployeeLegacy empLeg = empLegacyRepo.findOne(orgLeg.getEmpId());
            if (empLeg != null) {
                orgLeg.setName(empLeg.getName());//jay:replace org-employee name with real employee name
                nameReplacedRegistry.put(orgLeg.getEmpId(), orgLeg.getName());
            }
        }

        List<OrgnizationEmployee> resultNodes = TreeHelper.templateBuildTree(OrgnizationEmployeeLegacyTreeAdapter.adapt(orgLegs), OrgnizationEmployee.class, nameReplacedRegistry);
        return resultNodes;
    }

    @Override
    public OrgnizationEmployee findByEmployeeId(Long empId) {
        EmployeeLegacy empLeg = empLegacyRepo.findOne(empId);
        OrgnizationEmployeeLegacy orgEmpLeg = orgEmpLegacyRepo.findByEmpId(empId);
        OrgnizationEmployee rtnEmp = new OrgnizationEmployee(empId, orgEmpLeg.getOrgCode(), empLeg.getName());
        rtnEmp.setEmpId(empId);
        return rtnEmp;
    }

    /**
     * Ridicules performance
     */
    @Deprecated
    @Override
    @Transactional(propagation = Propagation.SUPPORTS)
    public List<ProjectEmployee> findSubordinateEmployeesOfUser(User tryUsingCurrentLoggedInUser) {
        Validate.notNull(tryUsingCurrentLoggedInUser.getEmployee(), "用户对应的雇员信息不能为空");
        Long empIdOfUser = tryUsingCurrentLoggedInUser.getEmployee().getEmpId();
        return findSubordinateEmployeesByEmpId(empIdOfUser);
    }

    @Deprecated
    @Override
    @Transactional(propagation = Propagation.SUPPORTS)
    public List<ProjectEmployee> findSubordinateEmployeesByEmpId(Long empId) {
        List<Project> projs = projRepo.findByParticipatedEmployeeId(empId);
        List<ProjectEmployee> rtnProjEmps = new LinkedList<ProjectEmployee>();
        for (Project proj : projs) {
            ProjectEmployee emp = proj.getParticipatedEmployeeBy(empId);
            dumpTree(emp, rtnProjEmps);
        }
        return rtnProjEmps;
    }

    public static <TK extends Serializable> void dumpTree(TreeNodeWithChilds<TK> entryNode, List<ProjectEmployee> projEmps) {
        if (entryNode == null || CollectionUtils.isEmpty(entryNode.getChilds())) {
            return;//touch leaf node
        } else {
            for (TreeNodeWithChilds<TK> childNode : entryNode.getChilds()) {
                projEmps.add((ProjectEmployee) childNode);
                dumpTree(childNode, projEmps);
            }
        }
    }

    @SuppressWarnings("unchecked")
    @Override
    public List<String[]> getProjectCodeLoginNameDisplayName_subOf(String employeeId) {
        String cacheKey = "getProjectCodeLoginNameDisplayName_subOf" + ":" + employeeId;
        List<String[]> resultList = (List<String[]>) cache.get(cacheKey);
        if (resultList != null) return resultList;
        String projectIdString = projectService.getUserInvolvedProjectIdString(employeeId);
        StringBuilder sql = new StringBuilder(500);
        sql.append("\n select pe.id, pe.parent, p.code, u.login_name, u.display_name, pe.emp_id");
        sql.append("\n from dm_project_employee pe");
        sql.append("\n join dm_project p on p.id=pe.project");
        sql.append("\n join dm_user u on u.emp_pk=pe.emp_id");
        sql.append("\n where pe.project in (").append(projectIdString).append(')');
        List<Object[]> list = entityManager.createNativeQuery(sql.toString()).getResultList();
        List<ProjectUser> currentUserRelated = new ArrayList<ProjectUser>(list.size());
        Map<String, ProjectUser> projectUserMap = new HashMap<String, ProjectUser>();
        for (Object[] v : list) {
            String projEmpId = String.valueOf(v[0]);
            String parentId = String.valueOf(v[1]);
            String projectCode = String.valueOf(v[2]);
            String loginName = String.valueOf(v[3]);
            String displayName = String.valueOf(v[4]);
            String empId = String.valueOf(v[5]);
            ProjectUser projectUser = new ProjectUser(projEmpId, parentId, projectCode, loginName, displayName);
            projectUserMap.put(projectCode + ":" + projEmpId, projectUser);
            if (employeeId.equals(empId)) {
                currentUserRelated.add(projectUser);
            }
        }
        for (ProjectUser projectUser : projectUserMap.values()) {
            if (projectUser.parentProjEmpId != null) {
                ProjectUser parent = projectUserMap.get(projectUser.projectCode + ":" + projectUser.parentProjEmpId);
                projectUser.setParent(parent);
            }
        }
        resultList = new LinkedList<String[]>();
        for (ProjectUser projectUser : currentUserRelated) {
            projectUser.fillList(resultList);
        }
        cache.set(cacheKey, resultList);
        return resultList;
    }

    private static class ProjectUser {
        String projEmpId;
        String parentProjEmpId;
        String projectCode;
        String loginName;
        String displayName;
        Set<ProjectUser> children = new HashSet<ProjectUser>();
        ProjectUser parent;

        private ProjectUser(String projEmpId, String parentProjEmpId, String projectCode, String loginName, String displayName) {
            this.projEmpId = projEmpId;
            this.parentProjEmpId = parentProjEmpId;
            this.projectCode = projectCode;
            this.loginName = loginName;
            this.displayName = displayName;
        }

        void setParent(ProjectUser parent) {
            if (parent != null) {
                this.parent = parent;
                parent.children.add(this);
            }
        }

        void fillList(List<String[]> list) {
            list.add(new String[] {projectCode, loginName, displayName});
            for (ProjectUser child : children) {
                child.fillList(list);
            }
        }
    }
}
